/*
 * uploader.c
 *
 *  Created on: 03.04.2011
 *      Author: mcsulits
 */

#include "uploader.h"
#include "uart_usb_lib.h"
#include "gamepad.h"
#include "JVM_ClassTable.h"
#include "flashc.h"
#include "board.h"
#include "JVM.h"

typedef enum {
	WAITING_FOR_COMMAND,
	WAITING_FOR_UPLOAD_PARAMETERS,
	UPLOADING
} UploaderState;

/*
 * The commands that may be sent from and to the host.
 */
typedef enum {
	NONE = 0,
	GET_AVAILABLE_MEMORY = 1, // returns the maximum amount of memory available for jvm programs
	UPLOAD_CODE = 2
} UploaderCommand;

/*
 * The replys send by the upoader to the host.
 */
typedef enum {
	REPLY_SUCCESS,
	REPLY_FAILURE
} UploaderReply;

/*
 * Give simple access to single bytes of an uint32.
 */
typedef union {
   uint8_t byte[4];
   uint32_t word;
} uint32_bytes;

/*
 * The parameters neccesary for uploading.
 */
typedef struct {
	uint32_t size;
	uint32_t checksum;
} UploadParameters;

/*
 * The current state of the uploader.
 */
UploaderState CurrentUploaderState;


void uploader_init(){
	CurrentUploaderState = WAITING_FOR_COMMAND;
}


void uploader_task(){

	/*
	 * State information for program uploads.
	 */
	static UploadParameters uploadParameters;
	static int uploadParametersBytesReceived = 0;

	static int uploadBytesReceived = 0;


	if(CurrentUploaderState == WAITING_FOR_COMMAND){

		// try to fetch a command from usb
		if(uart_usb_test_hit(&uart_usb_upload)){
			UploaderCommand UploaderRemoteCommand = uart_usb_getchar(&uart_usb_upload);

			//
			// handle received command
			//
			if(UploaderRemoteCommand == GET_AVAILABLE_MEMORY){

				uart_usb_putchar(&uart_usb_upload, REPLY_SUCCESS);

				uint32_bytes availableMemory;
				availableMemory.word = JVM_CLASS_AREA_SIZE;

				uart_usb_putchar(&uart_usb_upload, availableMemory.byte[0]);
				uart_usb_putchar(&uart_usb_upload, availableMemory.byte[1]);
				uart_usb_putchar(&uart_usb_upload, availableMemory.byte[2]);
				uart_usb_putchar(&uart_usb_upload, availableMemory.byte[3]);

				uart_usb_flush(&uart_usb_upload);
			} else if(UploaderRemoteCommand == UPLOAD_CODE){
				uart_usb_putchar(&uart_usb_upload, REPLY_SUCCESS);
				uart_usb_flush(&uart_usb_upload);

				CurrentUploaderState = WAITING_FOR_UPLOAD_PARAMETERS;
				uploadParametersBytesReceived = 0;
				jvm_running = false; // disable jvm because we're going to upload
			} else {
				// invalid command received -> reset
				uart_usb_putchar(&uart_usb_upload, REPLY_FAILURE);
				uart_usb_flush(&uart_usb_upload);

				uploader_init();
			}
		}
	} else if(CurrentUploaderState == WAITING_FOR_UPLOAD_PARAMETERS){
		// received all upload parameters?
		if(uploadParametersBytesReceived == sizeof(UploadParameters)){

			if(JVM_CLASS_AREA_SIZE < uploadParameters.size){
				// error: invalid upload size
				uart_usb_putchar(&uart_usb_upload, REPLY_FAILURE);
				uart_usb_flush(&uart_usb_upload);

				CurrentUploaderState = WAITING_FOR_COMMAND;
				jvm_running = true; // reenable jvm because we didn't upload anything yet
			} else {
				uart_usb_putchar(&uart_usb_upload, REPLY_SUCCESS);
				uart_usb_flush(&uart_usb_upload);

				CurrentUploaderState = UPLOADING;
				uploadBytesReceived = 0;
			}
		} else { // otherwise just wait for more data
			uint8_t receivedByte = uart_usb_getchar(&uart_usb_upload);
			*((uint8_t *)&uploadParameters + uploadParametersBytesReceived) = receivedByte;
			++uploadParametersBytesReceived;
		}
	} else if(CurrentUploaderState == UPLOADING){

		const int uploadBufferSize = 1024;
		uint8_t uploadBuffer[uploadBufferSize];
		int bytesReceived = 0;

		while(bytesReceived < uploadBufferSize &&
			  uploadBytesReceived + bytesReceived < uploadParameters.size &&
			  uart_usb_test_hit(&uart_usb_upload)){
			uploadBuffer[bytesReceived++] = uart_usb_getchar(&uart_usb_upload);
		}

		flashc_memcpy(
			JVM_ClassTable_getRawPointer() + uploadBytesReceived,
			&uploadBuffer,
			bytesReceived,
			true
		);

		uploadBytesReceived += bytesReceived;

//		uint8_t receivedByte = uart_usb_getchar(&uart_usb_upload);
//
//		flashc_memcpy(
//			JVM_ClassTable_getRawPointer() + uploadBytesReceived,
//			&receivedByte,
//			sizeof(receivedByte),
//			true
//		);
//
//		++uploadBytesReceived;

		if(uploadBytesReceived >= uploadParameters.size){
			// TODO: checksum data and notify host about success or failure
			uart_usb_putchar(&uart_usb_upload, REPLY_SUCCESS);
			uart_usb_flush(&uart_usb_upload);

			CurrentUploaderState = WAITING_FOR_COMMAND;

			// reinitialize jvm and enable it again
			JVM_init();
			jvm_running = true;
		}
	}

}
